package com.cm.controller;

import cn.hutool.core.date.StopWatch;
import cn.hutool.core.thread.ExecutorBuilder;
import cn.hutool.core.thread.NamedThreadFactory;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cm.config.MqttConfig;
import com.cm.domain.GainCoreDTO;
import com.cm.domain.PersonnelOrientationVO;
import com.cm.domain.RangingCoreDTO;
import com.cm.mapper.PersonnelOrientationMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author chenming
 * @description
 * @create: 2022-11-25
 */
@RestController
@EnableScheduling
@Slf4j
public class PushPointController {

    @Autowired
    private PersonnelOrientationMapper personnelOrientationMapper;

    @Autowired
    private MqttConfig mqttConfig;

    final Object lock = new Object();

    ThreadPoolExecutor poolExecutor = ExecutorBuilder.create()
            .setCorePoolSize(300)
            .setMaxPoolSize(1000)
            .setWorkQueue(new LinkedBlockingQueue<>(100000))
            .setThreadFactory(new NamedThreadFactory("Ready-", false))
            .build();

    private Map<Date, List<PersonnelOrientationVO>> fakeData;

    private Date start;
    private Date end;

    public static final String TOF_TOPIC = "/EH100602/dev/bs/tof/dist/";


    @PostConstruct
    public void init() {
        StopWatch stopWatch = new StopWatch();

        stopWatch.start("归类查询");
        LambdaQueryWrapper<PersonnelOrientationVO> condition = new LambdaQueryWrapper<>();
        condition.select(PersonnelOrientationVO::getCardNumber);
        condition.groupBy(PersonnelOrientationVO::getCardNumber);
        List<PersonnelOrientationVO> personnelOrientationVOS = personnelOrientationMapper.selectList(condition);
        stopWatch.stop();

        stopWatch.start("分类查询");
        List<CompletableFuture<List<PersonnelOrientationVO>>> completableFutures = new ArrayList<>();
        List<PersonnelOrientationVO> result = new ArrayList<>();
        AtomicInteger count = new AtomicInteger(1);
        for (PersonnelOrientationVO personnelOrientationVO : personnelOrientationVOS) {
            completableFutures.add(CompletableFuture.supplyAsync(() -> {
                        LambdaQueryWrapper<PersonnelOrientationVO> tmp = new LambdaQueryWrapper<>();
                        tmp.eq(PersonnelOrientationVO::getCardNumber, personnelOrientationVO.getCardNumber());
                        return personnelOrientationMapper.selectList(tmp);
                    }, poolExecutor)
                    .whenCompleteAsync((r, e) -> {
                        synchronized (lock) {
                            BigDecimal index = BigDecimal.valueOf(count.getAndIncrement()).divide(BigDecimal.valueOf(personnelOrientationVOS.size()), 2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100));
                            log.warn("数据已完成：{}%", index);
                            result.addAll(r);
                        }
                    }).exceptionally(throwable -> {
                        throwable.printStackTrace();
                        return null;
                    }));
        }
        CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).join();
        fakeData = result.stream().collect(Collectors.groupingBy(PersonnelOrientationVO::getRecordTime));
        stopWatch.stop();

        stopWatch.start("时间戳排序");
        List<Date> dateList = new ArrayList<>(fakeData.keySet());
        dateList.sort(Comparator.comparing(Date::getTime));
        start = dateList.get(0);
        end = dateList.get(dateList.size() - 1);
        stopWatch.stop();

        log.info(stopWatch.prettyPrint(TimeUnit.MILLISECONDS));
    }

    int count = 0;

    @Scheduled(cron = "0/1 * * * * ?")
    public void push() {
        List<PersonnelOrientationVO> personnelOrientationVOS = fakeData.get(DateUtils.addSeconds(start, count++));
        long timeStamp = System.currentTimeMillis() / 1000;
        for (PersonnelOrientationVO personnelOrientationVO : personnelOrientationVOS) {

            GainCoreDTO gainCoreDTO = new GainCoreDTO();
            gainCoreDTO.setAll_bs_num(1);
            gainCoreDTO.setBs_addr(Long.valueOf(personnelOrientationVO.getStationNumber()));
            gainCoreDTO.setRefresh_rate(1D);
            Map<String, RangingCoreDTO> rList = new HashMap<>();
            RangingCoreDTO rangingCoreDTO = new RangingCoreDTO();
            rangingCoreDTO.setBetween_antenna(0);
            rangingCoreDTO.setChannel(personnelOrientationVO.getDirection());
            rangingCoreDTO.setRange_bs_addr(gainCoreDTO.getBs_addr());
            rangingCoreDTO.setRange_dist(personnelOrientationVO.getRangeDist().intValue());
            rangingCoreDTO.setRssi(10);
            rangingCoreDTO.setTime_calibrate(1);
            rList.put(personnelOrientationVO.getStationNumber(), rangingCoreDTO);
            gainCoreDTO.setRlist(rList);
            gainCoreDTO.setTag_id(Long.valueOf(personnelOrientationVO.getCardNumber()));
            gainCoreDTO.setTimestamp(timeStamp);
            gainCoreDTO.setTof_sn((int) timeStamp % 1000000);

            mqttConfig.publish(TOF_TOPIC + personnelOrientationVO.getCardNumber(), JSON.toJSONString(gainCoreDTO), 1, false);
        }
    }

    @GetMapping("/getCount")
    public int getCount() {
        return count;
    }

    @PostMapping("/setCount")
    public int setCount(int newCount) {
        count = newCount;
        return count;
    }
}
